
/*
 * @项目名称: 模板开发
 * @FilePath: /meek/src/geometry/support/geometryutil.js
 * @Author: 1106408264@qq.com
 * @Date: 2017-05-25T09:25:09+08:00
 * @LastEditors: yangxianghong@datatang.com
 * @LastEditTime: 2023-07-13 16:20:32
 * Copyright (c) 2018-2022 
 */

/**
 * Created by zhangyong on 2017/5/23.
 */

import Geometry from '../../geometry/geometry'
import dSimplify from "simplify-js";
import coordinate from '../../utils/coordinate';
import Line from '../line'

/**
 * 判断两个点是否相交
 *
 * @method pointIntersectPoint
 * @param pointA
 * @param pointB
 * @param tolerance {Number}
 * @returns {Boolean}
 */
export function pointIntersectPoint(pointA, pointB, tolerance) {
  const toleranceDistance = tolerance ? tolerance : 2

  const distance = squaredDistance(pointA.x, pointA.y, pointB.x, pointB.y)
  if (distance <= toleranceDistance) {
    return true
  }

  return false
}

/**
 * 判断点是否与线相交
 *
 * @method pointIntersectLine
 * @param pointA
 * @param line
 * @param tolerance {Number}
 * @returns {Boolean}
 */
export function pointIntersectLine(pointA, line, tolerance) {
  const path = line.path
  const squaredSegmentDistanceFn = squaredSegmentDistance
  const x = pointA.x
  const y = pointA.y

  let find = false
  for (let i = 0, ii = path.length - 1; i < ii; i++) {
    let nowP = path[i]
    let nextP = path[i + 1]
    let distance = squaredSegmentDistanceFn(x, y, nowP.x, nowP.y, nextP.x, nextP.y)
    distance = Math.sqrt(distance)
    if (distance <= tolerance) {
      find = true
      break
    }
  }

  return find
}

/**
 * Return the square of the closest distance between the point
 * and the line segment.
 * @param x
 * @param y
 * @param x1
 * @param y1
 * @param x2
 * @param y2
 * @returns {number}
 */
export function squaredSegmentDistance(x, y, x1, y1, x2, y2) {
  const dx = x2 - x1
  const dy = y2 - y1
  if (dx !== 0 || dy !== 0) {
    let t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy)
    if (t > 1) {
      x1 = x2
      y1 = y2
    } else if (t > 0) {
      x1 += dx * t
      y1 += dy * t
    }
  }

  return squaredDistance(x, y, x1, y1)
}

/**
 *
 * @param coordinate
 * @param segment
 * @returns {number}
 */
export function squaredDistanceToSegment(coordinate, segment) {
  const closestCoordinate = closestOnSegment(coordinate, segment)
  return squaredDistance(coordinate[0], coordinate[1], closestCoordinate[0], closestCoordinate[1])
}

/**
 * 判断两个坐标是否相等
 *
 * @method equals
 * @param coordinate1
 * @param coordinate2
 * @returns {Boolean}
 */
export function equals(coordinate1, coordinate2) {
  let equals = true
  for (let i = coordinate1.length - 1; i >= 0; --i) {
    if (coordinate1[i] != coordinate2[i]) {
      equals = false
      break
    }
  }

  return equals
}

/**
 * 计算两点之间的距离的平方
 *
 * @method squaredDistance
 * @param x1
 * @param y1
 * @param x2
 * @param y2
 * @returns {Number} squared distance
 */
export function squaredDistance(x1, y1, x2, y2) {
  const dx = x2 - x1
  const dy = y2 - y1
  return dx * dx + dy * dy
}

/**
 * 计算两点之间的距离
 *
 * @method distance
 * @param coord1
 * @param coord2
 * @returns {Number}
 */
export function distance(coord1, coord2) {
  return Math.sqrt(squaredDistance(coord1[0], coord1[1], coord2[0], coord2[1]))
}

/**
 *
 * @param coordinate {Array}
 * @param segment
 * @returns {Array}
 */
export function closestOnSegment(coordinate, segment) {
  const x0 = coordinate[0]
  const y0 = coordinate[1]
  const start = segment[0]
  const end = segment[1]
  const x1 = start[0]
  const y1 = start[1]
  const x2 = end[0]
  const y2 = end[1]
  const dx = x2 - x1
  const dy = y2 - y1

  const along = (dx === 0 && dy === 0) ? 0 :
    ((dx * (x0 - x1)) + (dy * (y0 - y1))) / ((dx * dx + dy * dy) || 0)

  let x, y
  if (along <= 0) {
    x = x1
    y = y1
  } else if (along >= 1) {
    x = x2
    y = y2
  } else {
    x = x1 + along * dx
    y = y1 + along * dy
  }

  return [x, y]
}

/**
 *
 * @param flatCoordinates
 * @param offset
 * @param ends
 * @param stride
 * @param x
 * @param y
 * @returns {oolean}
 */
export function linearRingsContainsXY(flatCoordinates, offset, ends, stride, x, y) {
  if (ends.length === 0) {
    return false
  }

  if (!linearRingContainsXY(flatCoordinates, offset, ends[0], stride, x, y)) {
    return false
  }

  let i, ii
  for (i = 1, ii = ends.length; i < ii; ++i) {
    if (linearRingContainsXY(flatCoordinates, ends[i - 1], ends[i], stride, x, y)) {
      return false
    }
  }

  return true
}

/**
 *
 * @param flatCoordinates
 * @param offset
 * @param end
 * @param stride
 * @param x
 * @param y
 * @returns {boolean}
 */
export function linearRingContainsXY(flatCoordinates, offset, end, stride, x, y) {
  // http://geomalgorithms.com/a03-_inclusion.html
  // Copyright 2000 softSurfer, 2012 Dan Sunday
  // This code may be freely used and modified for any purpose
  // providing that this copyright notice is included with it.
  // SoftSurfer makes no warranty for this code, and cannot be held
  // liable for any real or imagined damage resulting from its use.
  // Users of this code must verify correctness for their application.
  let wn = 0
  let x1 = flatCoordinates[end - stride]
  let y1 = flatCoordinates[end - stride + 1]
  for (; offset < end; offset += stride) {
    let x2 = flatCoordinates[offset]
    let y2 = flatCoordinates[offset + 1]

    if (y1 <= y) {
      if (y2 > y && ((x2 - x1) * (y - y1)) - ((x - x1) * (y2 - y1)) > 0) {
        wn++
      }
    } else if (y2 <= y && ((x2 - x1) * (y - y1)) - ((x - x1) * (y2 - y1)) < 0) {
      wn--
    }

    x1 = x2
    y1 = y2
  }

  return wn !== 0
}

/**
 * 计算在 AB 延长线上的距离点 B d 距离的点
 * @param xa A 点的 X
 * @param ya A 点的 Y
 * @param xb B 点的 X
 * @param yb B 点的 Y
 * @param d 距离 B 点 d 距离
 * @returns {Array} 返回延长线上的点
 */
export function getPointInExtendedLineByDistanceFromAB(xa, ya, xb, yb, d) {
  let xab, yab
  let xbd
  let xd, yd

  xab = xb - xa
  yab = yb - ya

  if (xab === 0) {
    return [xb, yab > 0 ? yb + d : yb - d]
  }

  if (yab === 0) {
    return [xab > 0 ? xb + d : xb - d, yb]
  }

  xbd = Math.sqrt((d * d) / ((yab / xab) * (yab / xab) + 1))

  if (xab > 0) {
    xbd = Math.sqrt((d * d) / ((yab / xab) * (yab / xab) + 1))
  } else {
    xbd = -Math.sqrt((d * d) / ((yab / xab) * (yab / xab) + 1))
  }

  xd = xb + xbd
  yd = yb + yab / xab * xbd

  return [xd, yd]
}

export function deepClone(obj) {
  if (obj === undefined) {
    return undefined;
  }
  var newObj = obj.constructor === Array ? [] : {};
  if (typeof obj !== 'object') {
    return
  } else {
    for (var i in obj) {
      if (obj.hasOwnProperty(i)) {
        newObj[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i];
      }
    }
  }
  return newObj
}

export function simplify(geometry) {
  if (!geometry) {
    return geometry
  }

  let coordinates
  let indexes = []
  const geoType = geometry.geometryType
  if (geoType === Geometry.POLYGON && !geometry.fixedPointSize) {
    coordinates = geometry.getCoordinates()[0]
    console.log("len before", coordinates.length)
    //dpSimplify(coordinates, 0, coordinates.length - 2, indexes, d)
    coordinates = dSimplify(
      coordinates.map(coord => {
        return {x: coord[0], y: coord[1]}
      }),
      0.1
    );
    const newCoords = []
    coordinates.forEach((coord, index) => {
      if (!indexes.includes(index)) {
        newCoords.push([coord.x, coord.y]);
      }
    });
    console.log("len after", newCoords.length);
    geometry.setCoordinates([newCoords])
  } else if (geoType === Geometry.LINE) {
    coordinates = geometry.getCoordinates()
    console.log("len before", coordinates.length)
    if(Line.lineMode === 1){
      coordinates =
      dSimplify(
        coordinates.map(coord => {
          return { x: coord[0], y: coord[1] }
        }),
        0.1
      )
    } else {
      coordinates = coordinates.map(coord => {
        return { x: coord[0], y: coord[1] }
      })
    }
    //dpSimplify(coordinates, 0, coordinates.length - 1, indexes, d);
    const newCoords = []
    coordinates.forEach((coord, index) => {
      if (!indexes.includes(index)) {
        newCoords.push([coord.x, coord.y]);
      }
    });
    geometry.setCoordinates(newCoords)
    console.log("len after", newCoords.length);

    /*
    const sliceCoords = coordinates.slice(1, coordinates.length - 2)
    const newCoords = [coordinates[0]]

    for (let i = 0, len = sliceCoords.length; i < len; i += 2) {
      newCoords.push(sliceCoords[i])
    }

    newCoords.push(coordinates[coordinates.length - 1])
    geometry.setCoordinates(newCoords)
    */
  }
}

/**
 * Douglas-Peucker算法简化点
 * @param {*} coords
 * @param {*} start
 * @param {*} end
 */
function dpSimplify(coords, start, end, drop, D = 0.1) {
  // 阙值
  //const D = 0.1;

  const startP = coords[start];
  const endP = coords[end];
  if (!startP || !endP) {
    return;
  }

  let max = -1;
  let maxIndex = 0;
  const indexes = []
  for (let i = start + 1; i < end; i++) {
    indexes.push(i);

    const point = coords[i];
    const dis = squaredSegmentDistance(point[0], point[1], startP[0], startP[1], endP[0], endP[1]);
    if (dis > max) {
      max = dis;
      maxIndex = i;
    }
  }

  if (max < 0) {
    return;
  }

  if (max < D * D) {
    drop.push(...indexes);
  } else {
    dpSimplify(coords, start, maxIndex, drop);
    dpSimplify(coords, maxIndex, end, drop);
  }
}

/**
 * 计算在 AB 延长线上的距离点 B d 距离的点,如果AB重合，通过C点的垂线计算
 * @param xa A 点的 X
 * @param ya A 点的 Y
 * @param xb B 点的 X
 * @param yb B 点的 Y
 * @param xC C 点的 X
 * @param yC C 点的 Y
 * @param d 距离 B 点 d 距离
 * @returns {Array} 返回延长线上的点
 */
export function getPointInExtendedLineByDistanceFromABOrC(xa, ya, xb, yb, xc, yc, d) {
  let xab, yab
  let xbd
  let xd, yd
  xab = xb - xa
  yab = yb - ya
  xbd = Math.sqrt((d * d) / ((yab / xab) * (yab / xab) + 1))

  if (xab > 0) {
    xbd = Math.sqrt((d * d) / ((yab / xab) * (yab / xab) + 1))
  } else {
    xbd = -Math.sqrt((d * d) / ((yab / xab) * (yab / xab) + 1))
  }
  // if(xab == 0){
  //   xd = xb +d
  //   yd = yb +
  // }
  if (xab == 0) {
    let xac = xa - xc
    let yac = ya - yc
    let k = -xac / yac
    let ad = Math.sqrt((d * d) / (k * k) + 1)
    let xdd = xb + ad
    let ydd = yb + k * ad
    if ( yac === 0 ) {
      return [xdd, yb + d];
    }
    return [xdd, ydd]
  }
  xd = xb + xbd
  yd = yb + yab / xab * xbd

  return [xd, yd]

}

export function getYIntersetOfLine(p1, p2, x) {
  const [x1, y1] = p1;
  const [x2, y2] = p2;
  if (x1 === x2) {
    return 0;
  }

  const k = (y1 - y2) / (x1 - x2);
  const b = y1 - k * x1;
  return k * x + b;
}

function getDirection(y1, y2) {
  return (y2 - y1) / Math.abs(y2 - y1);
}

export function updateRectByRatio(coordinates, ratio, edgeIndex) {
  const [x1, y1] = coordinates[0];
  const [x2, y2] = coordinates[1];
  const [x3, y3] = coordinates[2];
  const [x4, y4] = coordinates[3];
  let width, height;

  const dir = getDirection(y1, y4);

  if (edgeIndex == 0 || edgeIndex == 1) {
    width = Math.abs(x3 - x4);
    height = width / ratio;

    coordinates[0] = [x4, y4 - dir * height];
    coordinates[1] = [x3, y3 - dir * height];
    coordinates[4] = coordinates[0]
  } else {
    width = Math.abs(x2 - x1);
    height = width / ratio;

    const dir = getDirection(y1, y4);

    coordinates[3] = [x1, y1 + dir * height];
    coordinates[2] = [x2, y2 + dir * height];
  }
}

export function updateTiltRectByRatio(coordinates, ratio, edgeIndex) {
  const [x1, y1] = coordinates[0];
  const [x2, y2] = coordinates[1];
  const [x3, y3] = coordinates[2];
  const [x4, y4] = coordinates[3];
  let width;
  let height;
  if (edgeIndex == 0 || edgeIndex == 2) {
    height = Math.sqrt(Math.pow(x2 - x3, 2) + Math.pow(y2 - y3, 2));
    width = height * ratio;
    if (x3 === x4) {
      coordinates[1] = [x1, y1 + width];
      coordinates[2] = [x4, y4 + width];
    } else {
      coordinates[1] = getCoordByHeight(coordinates[0], coordinates[1], width);
      coordinates[2] = getCoordByHeight(coordinates[3], coordinates[2], width);
    }

  } else {
    width = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
    height = width / ratio;

    if (x1 === x4) {
      coordinates[3] = [x1, y1 + height];
      coordinates[2] = [x2, y2 + height];
    } else {
      coordinates[3] = getCoordByHeight(coordinates[0], coordinates[3], height);
      coordinates[2] = getCoordByHeight(coordinates[1], coordinates[2], height);
    }
  }
}

export function getKAndBFromCoords(p1, p2) {
  const [x1, y1] = p1;
  const [x2, y2] = p2;
  let k;
  if (x1 !== x2) {
    k = (y1 - y2) / (x1 - x2);
  }
  const b = y1 - k * x1;
  return {
    k,
    b
  }
}

export function getCoordByHeight(p1, p2, height) {
  const [x1, y1] = p1;
  const [x2, y2] = p2;

  const { k, b } = getKAndBFromCoords(p1, p2);

  const a = Math.pow(k, 2) + 1;
  const b1 = 2*k*b - 2*k*y1 - 2*x1;
  const c = Math.pow(b-y1, 2) + Math.pow(x1, 2) - Math.pow(height, 2);

  let x = (Math.sqrt(Math.pow(b1, 2) - 4*a*c) - b1) / (2 * a);
  if ((x - x1) * (x2 - x1) < 0) {
    x = (-Math.sqrt(Math.pow(b1, 2) - 4*a*c) - b1) / (2 * a);
  }
  const y = k * x + b;
  return [x, y];
}

// 求  point到segment的距离
export function pointDistanceToSegment(point, segment) {
  const p1 = {
    x: segment[0][0],
    y: segment[0][1]
  }
  const p2 = {
    x: segment[1][0],
    y: segment[1][1]
  }
  const p3 = {
    x: point[0],
    y: point[1]
  }
  let len
  //如果p1.x==p2.x 说明是条竖着的线
  if (p1.x - p2.x == 0) {
    len = Math.abs(p3.x - p1.x)
  }
  else {
    let A = (p1.y - p2.y) / (p1.x - p2.x)
    let B = p1.y - A * p1.x

    len = Math.abs((A * p3.x + B - p3.y) / Math.sqrt(A * A + 1))
  }
  return len
}

//已知长方形对角线两端点的坐标和高度求其它两个角的坐标。 //https://www.oschina.net/question/3610747_2268556

// p2 横坐标大于于p4,
export function getTwoPointsBytwoPointsAndH(p2, p4, h) {

  const x2 = p2[0]
  const y2 = p2[1]
  const x4 = p4[0]
  const y4 = p4[1]
  const d = Math.sqrt((x4 - x2) * (x4 - x2) + (y4 - y2) * (y4 - y2))//对角线

  if (!(d > h)) {
    console.error(); ('这套数据无效： 高 ：' + h + ' 必须小于对角线段的长度: ' + Math.round(d))
    return
  }
  const r = d / 2
  const x_2 = r
  const y_2 = 0
  const x_4 = -r
  const y_4 = 0

  const beta = -Math.asin((y4 - y2) / d) //Ox’y’ 坐标系相对于坐标原点[x0,y,]旋转角度 β
  const x0 = x4 + Math.round(r * Math.cos(beta))
  const y0 = y4 + Math.round(r * Math.sin(beta))


  /* 计算 [x1,y1],[x3,y3]两点在 Ox’y’ 坐标系中的坐标 [x_1,y_1], [x_3,y_3] */
  const alpha = Math.atan(h / Math.sqrt((x_4 - x_2) * (x_4 - x_2) + (y_4 - y_2) * (y_4 - y_2) - h * h))

  const x_1 = x_4 + Math.round(h * Math.sin(alpha))
  const y_1 = y_4 - Math.round(h * Math.cos(alpha))
  const x_3 = x_2 - Math.round(h * Math.sin(alpha))
  const y_3 = y_2 + Math.round(h * Math.cos(alpha))

  /* 由Ox’y’坐标, 换算到 Oxy 坐标 */
  const x1 = Math.round(x_1 * Math.cos(beta) - y_1 * Math.sin(beta) + x0)
  const y1 = Math.round(x_1 * Math.sin(beta) + y_1 * Math.cos(beta) + y0)
  const x3 = Math.round(x_3 * Math.cos(beta) - y_3 * Math.sin(beta) + x0)
  const y3 = Math.round(x_3 * Math.sin(beta) + y_3 * Math.cos(beta) + y0)

  // 返回的x1>x3
  return [[x1, y1], [x3, y3]]
}

//已知p1 和 p2 ，是L上的两点，点3为直线外一点，求投影点p`。
export function getProjectionPointOnLine(p1, p2, p3) {
  p1 = {
    x: p1[0],
    y: p1[1]
  }
  p2 = {
    x: p2[0],
    y: p2[1]
  }
  p3 = {
    x: p3[0],
    y: p3[1]
  }
  //垂直x轴的情况
  if (p1.x - p2.x === 0) {
    return [p1.x, p3.y]
  }

  const A = (p1.y - p2.y) / (p1.x - p2.x)
  const B = (p1.y - A * p1.x)

  const m = p3.x + A * p3.y
  const px = (m - A * B) / (A * A + 1)
  const py = A * px + B
  return [px, py]
}

/**
 * 圆外一点与圆心连线与圆的交点
 * @param {*} circle { radius: this.r, center: {x: this.x, y: this.y}}
 * @param {*} line { p1: {x, y}, p2: {x: this.x, y: this.y} }
 * @returns
 */
export function inteceptCircleLineSeg(circle, line){
  var a, b, c, d, u1, u2, ret, retP1, retP2, v1, v2;
  v1 = {};
  v2 = {};
  v1.x = line.p2.x - line.p1.x;
  v1.y = line.p2.y - line.p1.y;
  v2.x = line.p1.x - circle.center.x;
  v2.y = line.p1.y - circle.center.y;
  b = (v1.x * v2.x + v1.y * v2.y);
  c = 2 * (v1.x * v1.x + v1.y * v1.y);
  b *= -2;
  d = Math.sqrt(b * b - 2 * c * (v2.x * v2.x + v2.y * v2.y - circle.radius * circle.radius));
  if(isNaN(d)){ // no intercept
      return [];
  }
  u1 = (b - d) / c;  // these represent the unit distance of point one and two on the line
  u2 = (b + d) / c;
  retP1 = {};   // return points
  retP2 = {}
  ret = []; // return array
  if(u1 <= 1 && u1 >= 0){  // add point if on the line segment
      retP1.x = line.p1.x + v1.x * u1;
      retP1.y = line.p1.y + v1.y * u1;
      ret[0] = retP1;
  }
  if(u2 <= 1 && u2 >= 0){  // second add point if on the line segment
      retP2.x = line.p1.x + v1.x * u2;
      retP2.y = line.p1.y + v1.y * u2;
      ret[ret.length] = retP2;
  }
  return ret;
}

// 根据已知两点 求直线斜率和截距
function getLineKB(p1, p2) {
  let k, b
  k = (p2.y - p1.y) / (p2.x - p1.x)
  b = p1.y - (p1.x * (p2.y - p1.y) / (p2.x - p1.x))
  return { k, b }
}

/**
 * 获取以P0为圆心，r为半径的圆与p1 ,p2组成直线的交点
 * @param {*} p0 圆心
 * @param {*} p1
 * @param {*} p2
 * @param {*} r 半径
 * @returns
 */
export function getLineCircleIntersection(p0, p1, p2, r) {
  let x1, y1, x2, y2
  let m = p0.x, n = p0.y // 圆心坐标m, n
  let { k, b } = getLineKB(p1, p2)
  let A, B, C // 转换一元二次方程Ax^2 + Bx + C = 0
  A = 1 + k * k
  B = 2 * k * (b - n) - 2 * m
  C = m * m + (b - n) * (b - n) - r * r
  x1 = (-B + Math.sqrt(Math.pow(B, 2) - 4 * A * C)) / (2 * A)
  y1 = k * x1 + b
  x2 = (-B - Math.sqrt(Math.pow(B, 2) - 4 * A * C)) / (2 * A)
  y2 = k * x2 + b
  let point1 = { x: x1, y: y1 }, point2 = { x: x2, y: y2 }
  return { point1, point2 }
}

export default {
  linearRingContainsXY,
  linearRingsContainsXY,
  pointIntersectPoint,
  squaredSegmentDistance,
  squaredDistance,
  closestOnSegment,
  getPointInExtendedLineByDistanceFromAB,
  getPointInExtendedLineByDistanceFromABOrC,
  equals,
  simplify,
  distance,
  pointIntersectLine,
  getYIntersetOfLine,
  updateTiltRectByRatio,
  updateRectByRatio,
  deepClone,
  getProjectionPointOnLine,
  getTwoPointsBytwoPointsAndH,
  pointDistanceToSegment,
  getLineCircleIntersection,
  inteceptCircleLineSeg,
}
